package com.falcon.core.auto;

import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

/** Cacheable 缓存公共配置类，比如key生成策略，序列化方式等。各模块儿自己的缓存，单独起配置类
 *
 * Cacheable 使用注意事项
 * 1. 必须提供 name 为 cacheManager 的 CacheManager 作为默认的 CacheManager
 * 2. @Cacheable 注解必须要加在方法上
 * 2. @Cacheable 注解必须设置 cacheNames 属性
 * 3. @Cacheable 可通过设置 cacheManager 参数选择不同的缓存器
 */
@Slf4j
@Configuration
@EnableCaching
public class CacheAutoConfig extends CachingConfigurerSupport {

    // 默认缓存 1小时
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.HOURS)
                .maximumSize(Integer.MAX_VALUE));
        return cacheManager;
    }

    @Bean
    public CacheManager localCache1s() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .maximumSize(Integer.MAX_VALUE));
        return cacheManager;
    }

    @Bean
    public CacheManager localCache1m() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(Integer.MAX_VALUE));
        return cacheManager;
    }

    @Bean
    public CacheManager localCache1h() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.HOURS)
                .maximumSize(Integer.MAX_VALUE));
        return cacheManager;
    }

    @Bean
    public CacheManager localCache1d() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.DAYS)
                .maximumSize(Integer.MAX_VALUE));
        return cacheManager;
    }

    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            Cacheable cacheable = method.getAnnotation(Cacheable.class);
            String cacheName = cacheable.cacheNames()[0];


            StringBuilder keySb = new StringBuilder();
            keySb.append(target.getClass().getName()).append(".").append(cacheName);

            for (Object param : params) {
                keySb.append(":").append(param);
            }

            String cacheKey = keySb.toString();
            log.info("[cache-manager] 缓存key: {}", cacheKey);
            return cacheKey;
        };
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("[cache-manager] key=[{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("[cache-manager] key=[{}]", key, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("[cache-manager] key=[{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("[cache-manager] ", e);
            }
        };
        return cacheErrorHandler;
    }
}
